---
title: Local Development
description: Learn how to use Alchemy's development mode to run your application locally.
sidebar:
  order: 4
---

import { Tabs, TabItem } from '@astrojs/starlight/components';

Alchemy's development mode provides a local development experience for Cloudflare Workers, featuring hot reloading, local resource emulation, and seamless integration with remote Cloudflare services.

:::caution
Development mode is currently in beta. Some features may not work as expected.
:::

## Get Started

To run Alchemy in development mode, use the [`alchemy dev`](/concepts/cli#dev) command to:

- Emulate Cloudflare Workers and associated resources locally
- Watch for and auto-apply changes to your infrastructure
- Hot reload Workers when you make changes to your runtime code


<Tabs syncKey="pkgManager">
  <TabItem label="bun">
    ```bash
    bun alchemy dev
    ```

    You can also specify additional options:

    ```bash
    # run dev mode with default settings
    bun alchemy dev

    # specify a stage
    bun alchemy dev --stage dev

    # use a custom script
    bun alchemy dev ./my-infra.ts

    # use an environment file
    bun alchemy dev --env-file .env.dev
    ```
  </TabItem>
  <TabItem label="npm">
    ```bash
    npx alchemy dev
    ```

    You can also specify additional options:

    ```bash
    # run dev mode with default settings
    npx alchemy dev

    # specify a stage
    npx alchemy dev --stage dev

    # use a custom script
    npx alchemy dev ./my-infra.ts

    # use an environment file
    npx alchemy dev --env-file .env.dev
    ```
  </TabItem>
  <TabItem label="pnpm">
    ```bash
    pnpm alchemy dev
    ```

    You can also specify additional options:

    ```bash
    # run dev mode with default settings
    pnpm alchemy dev

    # specify a stage
    pnpm alchemy dev --stage dev

    # use a custom script
    pnpm alchemy dev ./my-infra.ts

    # use an environment file
    pnpm alchemy dev --env-file .env.dev
    ```
  </TabItem>
  <TabItem label="yarn">
    ```bash
    yarn alchemy dev
    ```

    You can also specify additional options:

    ```bash
    # run dev mode with default settings
    yarn alchemy dev

    # specify a stage
    yarn alchemy dev --stage dev

    # use a custom script
    yarn alchemy dev ./my-infra.ts

    # use an environment file
    yarn alchemy dev --env-file .env.dev
    ```
  </TabItem>
</Tabs>


:::note
For more CLI options and commands, see the [CLI documentation](/concepts/cli).
:::

## Workers

In development mode, the [Cloudflare `Worker`](/guides/cloudflare-worker/) resource runs in Miniflare, a local environment that emulates the Cloudflare Workers runtime. By default, Workers run on the first available port beginning at `1337`:

```ts
const worker = await Worker("my-worker", {
  entrypoint: "worker.ts",
});
console.log(worker.url); // http://localhost:1337
```

You can also set a custom port for the Worker:

```ts
const worker = await Worker("my-worker", {
  entrypoint: "worker.ts",
  dev: {
    port: 3000,
  },
});
console.log(worker.url); // http://localhost:3000
```

## Tunnel

Route requests from the public internet to your locally running Worker via a Cloudflare Tunnel.

```ts
const worker = await Worker("my-worker", {
  entrypoint: "worker.ts",
  dev: {
    tunnel: true,
  },
});
```

:::tip
You must download and install `cloudflared` - see the [Cloudflare Tunnels documentation](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/).
:::

:::note
**How it works**: a temporary ["Quick Tunnel"](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/do-more-with-tunnels/trycloudflare/) is created and bound to the remote Worker (e.g. `my-worker`) which proxies requests to your locally running Worker via the Tunnel.
:::



## Bindings

Most Cloudflare bindings are automatically emulated in development mode, such as [`D1Database`](/providers/cloudflare/d1-database/), [`KVNamespace`](/providers/cloudflare/kv-namespace/), and [`R2Bucket`](/providers/cloudflare/bucket/):

```ts
const d1 = await D1Database("my-d1");
const kv = await KVNamespace("my-kv");
const r2 = await R2Bucket("my-r2");
const queue = await Queue("my-queue");
const worker = await Worker("my-worker", {
  entrypoint: "worker.ts",
  bindings: {
    D1: d1,
    KV: kv,
    R2: r2,
    QUEUE: queue,
  },
});
```

Secrets and plain-text bindings are also supported with no additional configuration:

```ts
const worker = await Worker("my-worker", {
  entrypoint: "worker.ts",
  bindings: {
    PLAIN_TEXT: "Hello, world!",
    API_KEY: alchemy.secret("pk_dev_1234567890"),
  },
});
```

## Remote Bindings

Some Cloudflare resources can be used as bindings in development mode. By default, resources that support local emulation run locally, but you can optionally set `dev.remote: true` to use the real deployed resource from Cloudflare instead.

```diff lang="ts"
const api = await Worker("my-api", {
  entrypoint: "api.ts",
  dev: {
+    remote: true,  // Use deployed worker instead of local emulation
  },
});
const kv = await KVNamespace("my-kv", {
  dev: {
+    remote: true,  // Use deployed KV instead of local emulation
  },
});
const worker = await Worker("my-worker", {
  entrypoint: "worker.ts",
  bindings: {
    KV: kv,
    API: api
  },
});
```

### Binding Support

| Resource | Local Emulation | Remote Binding | Documentation |
|----------|----------------|----------------|---------------|
| `Ai` | ❌ | ✅ | [Ai](/providers/cloudflare/ai/) |
| `AnalyticsEngine` | ✅ | ❌ | [AnalyticsEngine](/providers/cloudflare/analytics-engine/) |
| `Assets` | ✅ | ❌ | [Assets](/providers/cloudflare/assets/) |
| `BrowserRendering` | ❌ | ✅ | [BrowserRendering](/providers/cloudflare/browser-rendering/) |
| `Container` | ✅ | ✅ | [Container](/providers/cloudflare/container/) |
| `DispatchNamespace` | ❌ | ✅ | [DispatchNamespace](/providers/cloudflare/dispatch-namespace/) |
| `D1Database` | ✅ | ✅ | [D1Database](/providers/cloudflare/d1-database/) |
| `DurableObjectNamespace` | ❌ | ✅ | [DurableObjectNamespace](/providers/cloudflare/durable-object-namespace/) |
| `Hyperdrive` | ✅ | ❌ | [Hyperdrive](/providers/cloudflare/hyperdrive/) |
| `Images` | ✅ | ✅ | [Images](/providers/cloudflare/images/) |
| `KVNamespace` | ✅ | ✅ | [KVNamespace](/providers/cloudflare/kv-namespace/) |
| `Queue` | ✅ | ✅ | [Queue](/providers/cloudflare/queue/) |
| `Pipeline` | ✅ | ❌ | [Pipeline](/providers/cloudflare/pipeline/) |
| `RateLimit` | ✅ | ❌ | [RateLimit](/providers/cloudflare/rate-limit/) |
| `R2Bucket` | ✅ | ✅ | [R2Bucket](/providers/cloudflare/bucket/) |
| `VectorizeIndex` | ❌ | ✅ | VectorizeIndex | [VectorizeIndex](https://developers.cloudflare.com/vectorize/best-practices/create-indexes/) |
| `VersionMetadata` | ✅ | ❌ | [VersionMetadata](/providers/cloudflare/version-metadata/) |
| `Worker` | ✅ | ✅ | [Worker](/providers/cloudflare/worker/) |
| `Workflow` | ✅ | ❌ | [Workflow](/providers/cloudflare/workflow/) |

:::tip
Local emulation is enabled by default for resources that support it. Resources that can run in both local and remote modes can optionally set `dev.remote: true` to use the deployed resource instead.
:::

## Web Frameworks

Alchemy integrates with popular web frameworks, so you can use them with Alchemy's development mode and access local resources. Typically, you'll need to update your framework's configuration with the relevant Alchemy adapter or plugin. For example:

<Tabs syncKey="framework">
  <TabItem label="Astro">
    First, use the `Astro` resource in your `alchemy.run.ts` script:

    ```ts title="alchemy.run.ts"
    import { Astro } from "alchemy/cloudflare";

    const astro = await Astro("my-astro-app");
    ```

    Then, update your `astro.config.mjs` file:
    
    ```ts title="astro.config.mjs"
    import alchemy from "alchemy/cloudflare/astro";

    export default defineConfig({
      integrations: [alchemy()],
    });
    ```
  </TabItem>
  <TabItem label="React Router">
    First, use the `ReactRouter` resource in your `alchemy.run.ts` script:

    ```ts title="alchemy.run.ts"
    import { ReactRouter } from "alchemy/cloudflare";

    const reactRouter = await ReactRouter("my-react-router-app");
    ```

    Then, enable the `unstable_viteEnvironmentApi` flag in your `react-router.config.ts` file:

    ```ts title="react-router.config.ts"
    import type { Config } from "@react-router/dev/config";

    export default {
      ssr: true,
      future: {
        unstable_viteEnvironmentApi: true, // IMPORTANT: This allows Cloudflare to be used in the Vite environment.
      },
    } satisfies Config;
    ```

    And add the `alchemy/cloudflare/react-router` plugin to your `vite.config.ts`:

    ```ts title="vite.config.ts"
    import alchemy from "alchemy/cloudflare/react-router";
    import { reactRouter } from "@react-router/dev/vite";

    export default defineConfig({
      plugins: [alchemy(), reactRouter()],
    });
    ```
  </TabItem>
  <TabItem label="TanStack Start">
    First, use the `TanStackStart` resource in your `alchemy.run.ts` script:

    ```ts title="alchemy.run.ts"
    import { TanStackStart } from "alchemy/cloudflare";

    const tanstackStart = await TanStackStart("my-tanstack-start-app");
    ```

    Then, add the `alchemy/cloudflare/tanstack-start` plugin to your `vite.config.ts`:

    ```ts title="vite.config.ts"
    import alchemy from "alchemy/cloudflare/tanstack-start";
    import { tanstackStart } from "@tanstack/react-start/plugin/vite";
    import viteReact from "@vitejs/plugin-react";

    export default defineConfig({
      plugins: [
        alchemy(), 
        tanstackStart({
          target: "cloudflare-module",
          customViteReactPlugin: true,
        }),
        viteReact(),
      ],
    });
    ```
  </TabItem>
  <TabItem label="Vite">
    :::caution
    - If you are using React Router or TanStack Start, you must use the `alchemy/cloudflare/react-router` or `alchemy/cloudflare/tanstack-start` plugin instead of `alchemy/cloudflare/vite`.
    - If you are using SvelteKit or Nuxt, you will need to modify a different configuration file. See the SvelteKit and Nuxt guides for more information.
    :::

    First, use the `Vite` resource in your `alchemy.run.ts` script:

    ```ts title="alchemy.run.ts"
    import { Vite } from "alchemy/cloudflare";

    const vite = await Vite("my-vite-app");
    ```

    Then, add the `alchemy/cloudflare/vite` plugin to your `vite.config.ts`:

    ```ts title="vite.config.ts"
    import alchemy from "alchemy/cloudflare/vite";

    export default defineConfig({
      plugins: [alchemy()],
    });
    ```
  </TabItem>
  <TabItem label="BunSPA">
    :::note
    The BunSPA requires bun. It does NOT require Vite, Vite config or any Vite plugins.
    :::

    First, use the `BunSPA` resource in your `alchemy.run.ts` script:

    ```ts title="alchemy.run.ts"
    import { BunSPA } from "alchemy/cloudflare";

    const bunsite = await BunSPA("my-bun-app", {
      frontend: "src/index.html",
      entrypoint: "src/worker.ts",
    });
    ```

    Create a `bunfig.toml` file:

    ```toml title="bunfig.toml"
    [serve.static]
    env='BUN_PUBLIC_*'
    ```

    :::tip
    The `bunfig.toml` configuration allows Bun to expose `BUN_PUBLIC_*` environment variables to your frontend during development. This enables your client-side code to access the backend URL via `process.env.BUN_PUBLIC_BACKEND_URL`.
    :::

    BunSPA provides automatic integration:
    - **Frontend:** Bun's native dev server with hot module reloading
    - **Backend:** alchemy dev runs your Worker locally with full binding support
    - No additional configuration needed - just run `alchemy dev`
  </TabItem>
  <TabItem label="SvelteKit">
    First, use the `SvelteKit` resource in your `alchemy.run.ts` script:

    ```ts title="alchemy.run.ts"
    import { SvelteKit } from "alchemy/cloudflare";

    const svelteKit = await SvelteKit("my-sveltekit-app");
    ```

    Then, add the `alchemy/cloudflare/sveltekit` adapter to your `svelte.config.js` file:

    ```js title="svelte.config.js"
    import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
    import alchemy from 'alchemy/cloudflare/sveltekit';

    /** @type {import('@sveltejs/kit').Config} */
    const config = {
      preprocess: vitePreprocess(),
      kit: {
        adapter: alchemy()
      }
    };

    export default config;
    ```

    You should not need to update your `vite.config.ts` file.
  </TabItem>
  <TabItem label="Nuxt">
    First, use the `Nuxt` resource in your `alchemy.run.ts` script:

    ```ts title="alchemy.run.ts"
    import { Nuxt } from "alchemy/cloudflare";

    const nuxt = await Nuxt("my-nuxt-app");
    ```

    Then, add the `alchemy/cloudflare/nuxt` preset to your `nuxt.config.ts` file:

    ```ts title="nuxt.config.ts"
    import alchemy from "alchemy/cloudflare/nuxt";
    import { defineNuxtConfig } from "nuxt/config";

    export default defineNuxtConfig({
      nitro: {
        preset: "cloudflare_module",
        cloudflare: alchemy(),
      },
      modules: ["nitro-cloudflare-dev"],
    });
    ```
  </TabItem>
</Tabs>

:::tip
If you initialize a new project using the `alchemy create` command, the framework's configuration will be automatically updated with the relevant Alchemy adapter or plugin.
:::

## Limitations

:::caution
- Local Workers can push to remote queues, but cannot consume from them.
- Hyperdrive support is experimental. Hyperdrive configurations that use Cloudflare Access are not supported, and only configurations provisioned in the same alchemy.run.ts file will work. This is a limitation from Cloudflare that is actively being worked on.
- Container bindings with `dev: { remote: true }` cannot be used as local bindings in development mode.
- You may see “Connection refused” errors in the console when containers are starting up - these can be safely ignored.
:::